﻿using System;
using System.Web;
using Autofac;
using Autofac.Integration.Mvc;

namespace Actya.Web.Infrastructure.Autofac
{
	public class KeyedRequestLifeTimeScopeProvider : ILifetimeScopeProvider
	{
		private readonly string _key;
		readonly ILifetimeScope _container;
        readonly Action<ContainerBuilder> _configurationAction;

        /// <summary>
        /// Tag used to identify registrations that are scoped to the HTTP request level.
        /// </summary>
        internal static readonly object HttpRequestTag = "httpRequest";

		/// <summary>
		/// Initializes a new instance of the <see cref="RequestLifetimeScopeProvider"/> class.
		/// </summary>
		/// <param name="key">The that is used to store ILifetimeScope instances in the container.</param>
		/// <param name="container">The parent container, usually the application container.</param>
		/// <param name="configurationAction">Action on a <see cref="ContainerBuilder"/>
		/// that adds component registations visible only in HTTP request lifetime scopes.</param>
		public KeyedRequestLifeTimeScopeProvider(string key, ILifetimeScope container, Action<ContainerBuilder> configurationAction)
        {
            if (container == null) throw new ArgumentNullException("container");

			_key = key;
			_container = container;
            _configurationAction = configurationAction;

			// Associate this LifetimeScopeProvider with the Cms HttpModule to enable cleanup after a request has ended.
            CmsApplicationHttpModule.SetLifetimeScopeProvider(this);
        }

        /// <summary>
        /// Gets the global, application-wide container.
        /// </summary>
        public ILifetimeScope ApplicationContainer
        {
            get { return _container; }
        }

        /// <summary>
        /// Gets the configuration action that adds component registations 
        /// visible only in HTTP request lifetime scopes.
        /// </summary>
        protected Action<ContainerBuilder> ConfigurationAction
        {
            get { return _configurationAction; }
        }

        ILifetimeScope LifetimeScope
        {
            get
            {
            	return (ILifetimeScope)HttpContext.Current.Items[this._key];
            }
            set
            {
            	HttpContext.Current.Items[this._key] = value;
            }
        }

        /// <summary>
        /// Gets a HTTP request lifetime scope that services can be resolved from.
        /// </summary>
        /// <returns>A new or existing lifetime scope for the current HTTP request.</returns>
        public ILifetimeScope GetLifetimeScope()
        {
            if (HttpContext.Current == null)
                throw new InvalidOperationException("HttpContext is not available to get the LifetimeScope from.");

            if (LifetimeScope == null)
            {
                if ((LifetimeScope = GetLifetimeScopeCore()) == null)
                    throw new InvalidOperationException("LifetimeScope was not found in the current context.");
            }
            return LifetimeScope;
        }

        /// <summary>
        /// Ends the current HTTP request lifetime scope.
        /// </summary>
        public void EndLifetimeScope()
        {
            var lifetimeScope = LifetimeScope;
            if (lifetimeScope != null)
                lifetimeScope.Dispose();
        }

        /// <summary>
        /// Gets a lifetime scope for the current HTTP request. This method can be overridden
        /// to alter the way that the life time scope is constructed.
        /// </summary>
        /// <returns>A new lifetime scope for the current HTTP request.</returns>
        protected virtual ILifetimeScope GetLifetimeScopeCore()
        {
            return (ConfigurationAction == null)
                       ? ApplicationContainer.BeginLifetimeScope(HttpRequestTag)
                       : ApplicationContainer.BeginLifetimeScope(HttpRequestTag, ConfigurationAction);
        }
	}
}